package edu.unice.simul.vue.initialisation;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

import edu.unice.simul.conf.GeneralSettings;
import edu.unice.simul.controleur.initialisation.InitialisationControleur;
import edu.unice.simul.events.BlockChangedEvent;
import edu.unice.simul.listeners.PieceListener;
import edu.unice.simul.util.Block;
import edu.unice.simul.util.Dim;
import edu.unice.simul.vue.initialisation.modes_dessin.BlockType;
import edu.unice.simul.vue.initialisation.modes_dessin.SupportModesDessin;

/**
 * Panneau utilisé pour le déssin
 * 
 * @author Romain Ciaccafava
 * 
 */
public class DrawPanel extends JPanel implements SupportModesDessin,
		PieceListener {
	private static final long serialVersionUID = -4773484767513022835L;

	/**
	 * Mode de déssin "crayon"
	 */
	public static final int PEN = 1;
	/**
	 * Mode de déssin "ligne"
	 */
	public static final int LINE = 2;

	/**
	 * Mode de déssin actuel
	 */
	private int drawMode = PEN;

	private Dim size;
	private int blockSize = GeneralSettings.TAILLE_CASE;
	private BlockType mode = BlockType.MUR;
	private Graphics buffer;
	private Image image;
	private int actualX = -1, actualY = -1;
	private int oldX = -1, oldY = -1;

	private boolean mousePressed = false;

	private InitialisationControleur controleur;

	public DrawPanel(InitialisationControleur controleur, Dim dim) {
		this.size = dim;
		this.controleur = controleur;
		build();
	}

	/**
	 * Construit l'interface graphique et ajoute les écouteurs
	 */
	private void build() {
		setPreferredSize(new Dimension(size.getX() * blockSize, size.getY()
				* blockSize));
		setMinimumSize(new Dimension(size.getX() * blockSize, size.getY()
				* blockSize));

		addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseMoved(MouseEvent ev) {
				actualX = ev.getX() / blockSize;
				actualY = ev.getY() / blockSize;
				repaint();
			}

			@Override
			public void mouseDragged(MouseEvent ev) {
				if (drawMode == PEN) {
					oldX = actualX;
					oldY = actualY;
					actualX = ev.getX() / blockSize;
					actualY = ev.getY() / blockSize;

					addBlocks();
				} else {
					actualX = ev.getX() / blockSize;
					actualY = ev.getY() / blockSize;
					repaint();
				}
			}
		});

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent arg0) {
				if (drawMode == PEN)
					controleur.modifierBlockType(
							new Dim(arg0.getX() / blockSize, arg0.getY() / blockSize), mode);
			}

			@Override
			public void mousePressed(MouseEvent e) {
				if (drawMode == LINE) {
					oldX = e.getX() / blockSize;
					oldY = e.getY() / blockSize;
					mousePressed = true;
				}
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				if (drawMode == LINE) {
					mousePressed = false;
					addBlocks();
				}
			}
		});
	}

	/**
	 * Calcule quels blocks "passent" sur la ligne formée par p1 et p2
	 */
	private List<Block> calculateLine(Dim p1, Dim p2) {
		List<Block> blocks = new ArrayList<Block>();

		int origineX = -1, origineY = -1;
		int destX = -1, destY = -1;

		if (p1.getX() <= p2.getX()) {
			origineX = p1.getX();
			origineY = p1.getY();
			destX = p2.getX();
			destY = p2.getY();
		} else {
			origineX = p2.getX();
			origineY = p2.getY();
			destX = p1.getX();
			destY = p1.getY();
		}

		int vecteurX = destX - origineX;
		int vecteurY = destY - origineY;

		if (vecteurX == 0) {
			for (int i = Math.min(origineY, destY); i <= Math.max(origineY, destY); i++) {
				Block block = new Block(new Dim(origineX, i), mode);
				if (!blocks.contains(block))
					blocks.add(block);
			}
		} else {
			double coef = (double) vecteurY / (double) vecteurX;
			double pas = 0.01;
			for (int i = 0; i <= vecteurX / pas; i++) {
				double abs = (double) i * pas;
				int ord = (int) (coef * abs + (double) origineY);
				Block block = new Block(new Dim((int) abs + origineX, ord), mode);
				if (!blocks.contains(block))
					blocks.add(block);
			}
		}

		return blocks;
	}

	/**
	 * Ajoute les blocks en mode ligne quand le click est relâché
	 */
	private void addBlocks() {
		List<Block> list = calculateLine(new Dim(oldX, oldY), new Dim(actualX,
				actualY));

		for (Block block : list)
			controleur.modifierBlockType(block.getPos(), block.getType());
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);

		if (buffer == null) {
			Graphics2D g2D = (Graphics2D) g;
			/** Désactivation de l'anti-aliasing */
			g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_OFF);
			g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
					RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
			/** Demande de rendu rapide */
			g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_SPEED);
			g2D.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
					RenderingHints.VALUE_COLOR_RENDER_SPEED);
			g2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
					RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
			g2D.setRenderingHint(RenderingHints.KEY_DITHERING,
					RenderingHints.VALUE_DITHER_DISABLE);
			image = createImage(getWidth(), getHeight());
			buffer = image.getGraphics();

			buffer.setColor(Color.WHITE);
			buffer.fillRect(0, 0, getWidth(), getHeight());
		}
		g.drawImage(image, 0, 0, this);

		g.setColor(mode.getColor());

		if (mousePressed) {

			List<Block> list = calculateLine(new Dim(oldX, oldY), new Dim(actualX,
					actualY));
			for (Block block : list)
				g.fillRect(block.getPos().getX() * blockSize, block.getPos().getY()
						* blockSize, blockSize, blockSize);

		} else {
			if (actualX >= 0 && actualY >= 0) {
				switch (mode) {
				case HUMAIN:
					g.fillOval(actualX * blockSize, actualY * blockSize, blockSize,
							blockSize);
					break;
				default:
					g.fillRect(actualX * blockSize, actualY * blockSize, blockSize,
							blockSize);
				}
			}
		}
		g.setColor(Color.BLACK);
		g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
	}

	@Override
	public void modeMur() {
		this.mode = BlockType.MUR;
	}

	@Override
	public void modeSortie() {
		this.mode = BlockType.SORTIE;
	}

	@Override
	public void modeHumain() {
		this.mode = BlockType.HUMAIN;
	}

	@Override
	public void effacer() {
		this.mode = BlockType.NOTHING;
	}

	/**
	 * Méthode appellée lorsqu'un block de la pièce a été modifié
	 */
	@Override
	public void pieceChanged(BlockChangedEvent event) {
		buffer.setColor(event.getBlockType().getColor());
		switch (event.getBlockType()) {
		case HUMAIN:
			buffer.fillOval(event.getPos().getX() * blockSize, event.getPos().getY()
					* blockSize, blockSize, blockSize);
			break;
		default:
			buffer.fillRect(event.getPos().getX() * blockSize, event.getPos().getY()
					* blockSize, blockSize, blockSize);
		}
		repaint();
	}

	/**
	 * Méthode appellée lorsque la pièce est remise à zéro
	 */
	@Override
	public void pieceCleared() {
		buffer = null;
		repaint();
	}

	@Override
	public void pen() {
		drawMode = PEN;
	}

	@Override
	public void line() {
		drawMode = LINE;
	}
}
